package interceptor

import (
	"net/http"
	"strings"
	"sync"

	. "gitee.com/tomatomeatman/golang-repository/bricks/model"
	. "gitee.com/tomatomeatman/golang-repository/bricks/utils/function/app"
	. "gitee.com/tomatomeatman/golang-repository/bricks/utils/function/url"

	. "gitee.com/tomatomeatman/golang-repository/bricks/business/simple/ignoreurl"
	. "gitee.com/tomatomeatman/golang-repository/bricks/business/simple/login"
	. "gitee.com/tomatomeatman/golang-repository/bricks/business/simple/login/model"
	. "gitee.com/tomatomeatman/golang-repository/bricks/business/simple/userandright"

	"github.com/gin-gonic/gin"
)

var (
	interceptorOnce                sync.Once             //初始化锁
	sysIgnoreUrlByNotLogin         = ""                  //当前系统设置的忽略拦截路径-未登录前 配置app.InterceptorIgnore
	sysIgnoreUrlByLogined          = ""                  //当前系统设置的忽略拦截路径-登录后 配置app.InterceptorIgnoreLogined
	sysIgnoreUrlByNotLoginList     = map[string]string{} //当前系统设置的忽略拦截路径-未登录前
	sysIgnoreUrlByNotLoginLikeList = []string{}          //当前系统设置的忽略拦截路径(模糊)-未登录前
	sysIgnoreUrlByLoginedList      = map[string]string{} //当前系统设置的忽略拦截路径-登录后
	sysIgnoreUrlByLoginedLikeList  = []string{}          //当前系统设置的忽略拦截路径(模糊)-登录后
	InterceptorLock                sync.Mutex            //保存锁
)

// --拦截器--//
type Interceptor struct{}

// 引入操作
func (ic Interceptor) Import() {
	//fmt.Println("无意义,只为了能将模块引入程序")
}

// 初始化
func init() {
	go interceptorOnce.Do(interceptor_initVar)
}

// 初始化参数
func interceptor_initVar() {
	sysIgnoreUrlByNotLogin = AppUtil{}.ReadConfigKey("App", "InterceptorIgnore", "").(string)
	sysIgnoreUrlByLogined = AppUtil{}.ReadConfigKey("App", "InterceptorIgnoreLogined", "").(string)
}

/**
 * 拦截操作
 * @param w
 * @param r
 * @return
 */
func (inter Interceptor) Check(ctx *gin.Context) bool {
	interceptorOnce.Do(interceptor_initVar) //防止未初始化结束

	go LoginController{}.Heartbeat(ctx) //登录心跳操作

	if inter.isIgnoresByNotLogin(ctx) { //属于配置文件中的未登录时免拦截路径,因程序可能属于无数据库的情况,所以需要配置文件检查
		return true
	}

	i := inter.checkIgnoreUrl(ctx.Request.URL.Path, false, "") //验证是否可忽略路径模块,只能验证不用登录也能忽略的路径
	if i == 2 {                                                //1:没有使用'忽略路径模块'2:路径为'可忽略路径'3:不是可忽略的路径
		inter.getUserInfoByCookie(ctx) //取当前用户信息
		return true                    //属于免登录的免拦截路径则通过
	}

	switch inter.isInside(ctx) { //判断是否属于内部请求的路径 0:不是内部请求, 1:是内部请求并通过, 2:是内部请求但不通过
	case 1: //1:是内部请求并通过
		return true
	case 2: //2:是内部请求但不通过
		return false
	default: //0:不是内部请求
		break
	}

	if !inter.validLogin(ctx) { // 用户登录验证
		return false
	}

	sUserType := UrlUtil{}.GetParam(ctx, "sLoginUserType", "").(string)
	i = inter.checkIgnoreUrl(ctx.Request.URL.Path, true, sUserType) //验证是否可忽略路径模块,一并验证登录后才能忽略的路径
	if i == 2 {                                                     //1:没有使用'忽略路径模块'2:路径为'可忽略路径'3:不是可忽略的路径
		return true //属于登录后的免拦截路径则通过
	}

	//没有使用'忽略路径模块'或'不是可忽略的路径',必须交由权限进行控制访问
	if !inter.validRight(ctx) { //权限验证
		return false
	}

	return true
}

/**
 * 判断是否属于内部请求的路径; 0:不是内部请求, 1:是内部请求并通过, 2:是内部请求但不通过
 * 判断内部请求的依据:有内部请求密钥或请求地址为内部请求地址
 * @param w
 * @param r
 * @return
 */
func (inter Interceptor) isInside(ctx *gin.Context) int {
	servletPath := ctx.Request.URL.Path

	key := UrlUtil{}.GetParam(ctx, "sInsideKey", "").(string) //如果是内部请求,则内部密钥必须存在，从请求头获取内部密钥

	//--检查是否属于内部请求路径,如果是内部请求路径则检查访问key是否与当前系统一致,只要一致则可以通过--//
	if !strings.Contains(servletPath, "/inside/") { //包含'/inside/'的请求属于内部请求
		if "" == key { //不是内部请求路径,又不提供内部请求密钥,则返回"不是内部请求"
			return 0 //不是内部请求
		}

		appKey := AppUtil{}.ReadConfigKey("App", "InsideKey", "12345").(string)
		if ("" == appKey) || (key != appKey) { //有提供内部密钥,但是密钥错误,则返回"不是内部请求"
			return 0 //不是内部请求
		}

		//--对非内部请求的路径进行了内部请求操作,使用超管账号--//
		UrlUtil{}.AddAttrib(ctx, "sLoginUserId", "00000000")     //将对应的id赋予请求属性,用于后续请求操作
		UrlUtil{}.AddAttrib(ctx, "sLoginUserName", "superAdmin") //将对应的sName赋予请求属性,用于后续请求操作
		UrlUtil{}.AddAttrib(ctx, "sLoginUserNo", "0000")         //将对应的sNo赋予请求属性,用于后续请求操作
		UrlUtil{}.AddAttrib(ctx, "sLoginUserType", "admin")      //将对应的sType赋予请求属性,用于后续请求操作

		return 1 //内部请求操作
	}

	appKey := AppUtil{}.ReadConfigKey("App", "InsideKey", "12345").(string)
	if ("" == key) || (key != appKey) {
		ctx.JSONP(http.StatusOK, MsgEmity{}.Err(1000001, "内部请求密钥错误"))
		return 2 //密钥不符
	}

	//--使用内部请求时,一旦没有输入登录密钥则视为超管操作--//
	sCookie := UrlUtil{}.GetParam(ctx, "sCookie", "").(string) //获取request对象中的参数,优先: 头信息->参数-->属性

	if "" == sCookie { //确实没有登录
		UrlUtil{}.AddAttrib(ctx, "sLoginUserId", "00000000")     //将对应的id赋予请求属性,用于后续请求操作
		UrlUtil{}.AddAttrib(ctx, "sLoginUserName", "superAdmin") //将对应的sName赋予请求属性,用于后续请求操作
		UrlUtil{}.AddAttrib(ctx, "sLoginUserNo", "0000")         //将对应的sNo赋予请求属性,用于后续请求操作
		UrlUtil{}.AddAttrib(ctx, "sLoginUserType", "admin")      //将对应的sType赋予请求属性,用于后续请求操作
	} else {
		UrlUtil{}.AddAttrib(ctx, "sCookie", sCookie) //放入传递的参数
	}

	return 1
}

/**
 * 判断是否属于未登录下免拦截的路径
 * @param request
 * @param response
 * @return
 */
func (inter Interceptor) isIgnoresByNotLogin(ctx *gin.Context) bool {
	servletPath := ctx.Request.URL.Path

	if strings.Contains(sysIgnoreUrlByNotLogin, servletPath) { //访问'忽略路径的验证'方法必须跳过
		return true
	}

	if len(sysIgnoreUrlByNotLoginList) < 1 {
		array := strings.Split(sysIgnoreUrlByNotLogin, ",")

		InterceptorLock.Lock() //加锁

		for _, val := range array {
			if !strings.Contains(val, "*") {
				sysIgnoreUrlByNotLoginList[val] = ""
				continue
			}

			iEd := strings.Index(val, "*")
			val := val[:iEd-1]
			sysIgnoreUrlByNotLoginLikeList = append(sysIgnoreUrlByNotLoginLikeList, val)
			sysIgnoreUrlByNotLoginList[val] = ""
		}

		InterceptorLock.Unlock() //解锁
	}

	_, ok := sysIgnoreUrlByNotLoginList[servletPath] //明确的路径是否存在
	if ok {
		return true
	}

	if len(sysIgnoreUrlByNotLoginLikeList) > 0 {
		for _, val := range sysIgnoreUrlByNotLoginLikeList {
			if strings.Contains(servletPath, val) {
				return true
			}
		}
	}

	return false
}

/**
 * 用户登录验证
 * @param request
 * @param response
 * @return
 */
func (inter Interceptor) validLogin(ctx *gin.Context) bool {
	sCookie := UrlUtil{}.GetParam(ctx, "sCookie", "").(string) //获取request对象中的参数,优先: 头信息->参数-->属性

	if (sCookie == "") || ("NULL" == strings.ToUpper(sCookie)) { //如果没有登录密钥,则返回错误
		ctx.JSONP(http.StatusOK, MsgEmity{}.Err(1000002, "您还没有登录或登录已超时，请重新登录！"))
		return false
	}

	//--检查登录密钥--//
	me := (LoginController{}.Check(ctx)).(*MsgEmity)
	if !me.Gsuccess {
		ctx.JSONP(http.StatusOK, MsgEmity{}.Err(1000000+me.Gdata.(int), me.Gmsg))
		return false
	}

	me = LoginServer{}.GetLogin(sCookie)
	if !me.Gsuccess {
		ctx.JSONP(http.StatusOK, MsgEmity{}.Err(1100000+me.Gdata.(int), me.Gmsg))
		return false
	}

	loginUser := me.Gdata.(LoginUser)

	UrlUtil{}.AddAttrib(ctx, "sLoginUserId", loginUser.GsId)     //将对应的id赋予请求属性,用于后续请求操作
	UrlUtil{}.AddAttrib(ctx, "sLoginUserName", loginUser.GsName) //将对应的sName赋予请求属性,用于后续请求操作
	UrlUtil{}.AddAttrib(ctx, "sLoginUserNo", loginUser.GsNo)     //将对应的sNo赋予请求属性,用于后续请求操作
	UrlUtil{}.AddAttrib(ctx, "sLoginUserType", loginUser.GsType) //将对应的sType赋予请求属性,用于后续请求操作
	UrlUtil{}.AddAttrib(ctx, "sCookie", sCookie)                 //放入传递的参数

	//-- 含有附属信息则一并加入 --//
	if len(loginUser.Gattached) > 0 {
		for key, val := range loginUser.Gattached {
			UrlUtil{}.AddAttrib(ctx, key, val)
		}
	}

	return true
}

/**
 * 取用户信息
 */
func (inter Interceptor) getUserInfoByCookie(ctx *gin.Context) {
	sCookie := UrlUtil{}.GetParam(ctx, "sCookie", "").(string) //获取request对象中的参数,优先: 头信息->参数-->属性

	if (sCookie == "") || ("NULL" == strings.ToUpper(sCookie)) { //如果没有登录密钥,则不能获取
		return
	}

	//--检查登录密钥--//
	me := (LoginController{}.Check(ctx)).(*MsgEmity)
	if !me.Gsuccess {
		return
	}

	me = LoginServer{}.GetLogin(sCookie)
	if !me.Gsuccess {
		return
	}

	loginUser := me.Gdata.(LoginUser)

	UrlUtil{}.AddAttrib(ctx, "sLoginUserId", loginUser.GsId)     //将对应的id赋予请求属性,用于后续请求操作
	UrlUtil{}.AddAttrib(ctx, "sLoginUserName", loginUser.GsName) //将对应的sName赋予请求属性,用于后续请求操作
	UrlUtil{}.AddAttrib(ctx, "sLoginUserNo", loginUser.GsNo)     //将对应的sNo赋予请求属性,用于后续请求操作
	UrlUtil{}.AddAttrib(ctx, "sLoginUserType", loginUser.GsType) //将对应的sType赋予请求属性,用于后续请求操作
	UrlUtil{}.AddAttrib(ctx, "sCookie", sCookie)                 //放入传递的参数

	//-- 含有附属信息则一并加入 --//
	if len(loginUser.Gattached) > 0 {
		for key, val := range loginUser.Gattached {
			UrlUtil{}.AddAttrib(ctx, key, val)
		}
	}
}

/**
 * 用户权限验证
 * @param request
 * @param response
 * @return
 * @throws IOException
 */
func (inter Interceptor) validRight(ctx *gin.Context) bool {
	servletPath := ctx.Request.URL.Path

	sUserId := UrlUtil{}.GetAttrib(ctx, "sLoginUserId") //验证'是否登录'时已经添加

	me := UserAndRightService{}.CheckRight(ctx, sUserId, servletPath)
	if me.Gsuccess {
		return true //有权限
	}

	ctx.JSONP(http.StatusOK, MsgEmity{}.Err(1000006, "当前用户没有访问'", servletPath, "'的权限",
		"请检查1:是否在权限中添加了权限信息2:是否分配权限到具体角色或用户;3:若权限分配到了角色则是否已经将角色分配到用户"))

	return false //没有权限,也不是可忽略的路径,禁止
}

/**
 * 验证是否可忽略路径模块
 * @param url 要验证的路径
 * @param isMustLogin 是否必须登录
 * @param sUserType 待检验的用户类型
 * @return 1:没有使用'忽略路径模块';2:路径为'可忽略路径';3:不是可忽略的路径
 */
func (inter Interceptor) checkIgnoreUrl(url string, isMustLogin bool, sUserType string) int {
	if isMustLogin { //判断是否是在配置文件中要求登录后就能免拦截的路径
		if len(sysIgnoreUrlByLoginedList) < 1 {
			array := strings.Split(sysIgnoreUrlByLogined, ",")

			InterceptorLock.Lock() //加锁

			for _, val := range array {
				if !strings.Contains(val, "*") {
					sysIgnoreUrlByLoginedList[val] = ""
					continue
				}

				iEd := strings.Index(val, "*")
				val := val[:iEd-1]

				sysIgnoreUrlByLoginedLikeList = append(sysIgnoreUrlByLoginedLikeList, val)
				sysIgnoreUrlByLoginedList[val] = "" //明确的也属于
			}

			InterceptorLock.Unlock() //解锁
		}

		_, ok := sysIgnoreUrlByLoginedList[url] //明确的路径是否存在
		if ok {
			return 2
		}

		if len(sysIgnoreUrlByLoginedLikeList) > 0 {
			for _, val := range sysIgnoreUrlByLoginedLikeList {
				if strings.Contains(url, val) {
					return 2
				}
			}
		}
	}

	me := IgnoreURLService{}.CheckIgnoreUrl(url, isMustLogin, sUserType)
	if me.Gsuccess && (true == me.Gdata.(bool)) {
		return 2 //路径是可忽略路径
	}

	return 3 //不是可忽略的路径,禁止
}
